package com.ppc.utils;


import com.ppc.pmatch.ShareObj;
import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Pairing;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * @author dy
 * @date 2023/12/20
 * @descreption 产生公钥
 */
public class SecretShare {

  private Pairing elem;
  private boolean verbose = false;

  /**
   * @param element
   * @param verbose_status
   * @description
   */
  SecretShare(Pairing element, Boolean verbose_status) { //, boolean verbose_status = True
    elem = element;
    verbose = verbose_status;
  }


  /**
   * @param coeff
   * @param x
   * @return {@link Element }
   * @description
   */
  public Element P(List<Element> coeff, int x) {
    Element share = elem.getZr().newZeroElement(); // copy of element but mutable
    for (int i = 0; i < coeff.size(); i++) {
      Element temp = coeff.get(i).duplicate().mul((int) Math.pow(x, i)); // coeff[i] is imutable
      share.add(temp);
    }
    return share;
  }

  /**
   * @param secret
   * @param k
   * @param n
   * @return {@link ShareObj }
   * @description
   */
  public ShareObj genShares(Element secret, int k, int n) {
    if (k <= n) {
      List<Element> q = new ArrayList<Element>();
      for (int i = 0; i < k; i++) {
        q.add(elem.getZr().newRandomElement().getImmutable()); // immutable Element
      }
      q.set(0, secret);
      List<Element> shares = new ArrayList<Element>();
      for (int i = 0; i < n + 1; i++) {
        shares.add(P(q, i));
      }
      ShareObj shareObj = new ShareObj(q, shares);
      return shareObj;
    } else {
      return null;
    }
  }

  /**
   * @param dict
   * @return {@link Map }<{@link Element }, {@link Element }>
   * @description
   */
  public Map<Element, Element> recoverCoefficients(Set<Element> dict) {
    Map<Element, Element> coeff = new LinkedHashMap<Element, Element>();
    for (Element i : dict) {
      //System.out.println(i);
      Element result = elem.getZr().newOneElement();
      //System.out.println(result);
      for (Element j : dict) {
        //System.out.println(j);
        if (!i.isEqual(j)) { // i,j is immutable
          Element up = j.duplicate().negate();
          Element down = i.duplicate().sub(j);
          //System.out.println(up);
          //System.out.println(down);
          result.mul(up.duplicate().div(down)); // *= not **
          //System.out.println(up.duplicate().div(down));
          //System.out.println("result="+result);
        }
      }
      if (verbose) {
        System.out.println("coeff " + i + "=>" + result);
      }
      coeff.put(i, result);
    }
    return coeff;
  }


  /**
   * @param shares
   * @return {@link Element }
   * @description
   */
  public Element recoverSecret(Map<Element, Element> shares) {
    Set<Element> list = shares.keySet();
    if (verbose) {
      System.out.println("list\n" + list);
    }
    Map<Element, Element> coeff = recoverCoefficients(list);
    if (verbose) {
      System.out.println("coefficients：" + coeff); // 系数
    }
    Element secret = elem.getZr().newZeroElement(); // mutable
    //System.out.println("secret:"+secret);
    for (Element i : list) {
      secret.add(coeff.get(i).duplicate().mul(shares.get(i)));
      //System.out.println("secret:"+secret);
    }
    return secret;

  }

  /**
   * @param args
   * @description
   */// 初始化创建group，
  public static void main(String[] args) {
    int k = 3;
    int n = 4;
    Pairing group = PairingFactory.getPairing("a.properties");
    SecretShare s = new SecretShare(group, true);
    Element sec = group.getZr().newRandomElement().getImmutable();
    ShareObj shareObj = s.genShares(sec, k, n);
    List<Element> shares = shareObj.getShares();
    System.out.println("shares\n" + shares);
    Element K = shares.get(0);
    System.out.println("\nOriginal secret:" + K);

    Map<Element, Element> y = new LinkedHashMap<Element, Element>();
    y.put(group.getZr().newElement(1), shares.get(1));
    y.put(group.getZr().newElement(2), shares.get(2));
    y.put(group.getZr().newElement(3), shares.get(3));
    System.out.println(y);
    Element secret = s.recoverSecret(y);
    System.out.println(secret);
    if (K.isEqual(secret)) {
      System.out.println("\nSuccessfully recovered secret:" + secret); // expression is correct!!
    } else {
      System.out.println("\nCould not recover the secret!");
    }
  }


}
